package com.example.utility.crypto.sm2;

import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;

import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * @author 季舟
 * @Date 2024-12-25 13:43
 */
public class SM2EncryptionDemo {

    /**
     * 主程序入口
     * 本程序主要用于演示SM2密钥对的生成、使用公钥加密数据以及使用私钥解密数据的过程
     * @param args 命令行参数
     * @throws Exception 如果密钥对生成、加密或解密过程中发生错误，则抛出异常
     */
    public static void main(String[] args) throws Exception {
        // 生成RSA密钥对
        KeyPair keyPair = generateKeyPair();

        // 将公钥转换为Base64编码的字符串形式
        String publicKeyBase64 = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
        // 将私钥转换为Base64编码的字符串形式
        String privateKeyBase64 = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());

        // 打印公钥和私钥的Base64编码字符串
        System.out.println("公钥:"+ publicKeyBase64);
        System.out.println("私钥:"+ privateKeyBase64);

        // 定义待加密的原文数据
        final String orginalData = "12345678";
        // 打印原文数据
        System.out.println("原文:"+orginalData);

        // 使用公钥加密原文数据，并将加密后的数据转换为Base64编码的字符串形式
        byte[] encryptedData = encrypt(publicKeyBase64, orginalData);
        String encryptedDataBase64 = Base64.getEncoder().encodeToString(encryptedData);
        // 打印加密后的数据
        System.out.println("密文:"+encryptedDataBase64);

        // 使用私钥解密数据
        byte[] decryptedData = decrypt(privateKeyBase64, encryptedDataBase64);
        // 打印解密后的数据
        System.out.println("解密:"+new String(decryptedData));
    }

    /**
     * 生成密钥对
     *
     * 此方法用于生成基于椭圆曲线算法（EC）的密钥对，特别适用于SM2算法
     * 它利用Bouncy Castle Provider来支持SM2椭圆曲线参数
     *
     * @return KeyPair对象，包含公钥和私钥
     * @throws NoSuchAlgorithmException 如果算法名称不存在
     * @throws InvalidAlgorithmParameterException 如果算法参数无效
     */
    public static KeyPair generateKeyPair() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {

        // 创建EC算法的密钥对生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());

        /*
        // 以下代码被注释掉，可能是因为不需要随机化或者特定的椭圆曲线参数
        SecureRandom secureRandom = new SecureRandom();
        ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
        // 使用特定的椭圆曲线参数初始化密钥对生成器
        keyPairGenerator.initialize(sm2Spec);
        // 使用安全随机数和椭圆曲线参数初始化密钥对生成器
        keyPairGenerator.initialize(sm2Spec, secureRandom);
        */

        // 生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();

        // 返回生成的密钥对
        return keyPair;
    }

    /**
     * 使用公钥对原始数据进行加密
     *
     * @param publicKeyBase64 Base64编码的公钥字符串
     * @param orginalData     需要加密的原始数据字符串
     * @return 加密后的字节数组
     * @throws NoSuchAlgorithmException    如果算法不存在
     * @throws InvalidKeySpecException    如果密钥规范无效
     * @throws InvalidCipherTextException 如果密文无效
     */
    public static byte[] encrypt(String publicKeyBase64, String orginalData) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidCipherTextException {
        // 解码Base64编码的公钥，并生成X509EncodedKeySpec对象
        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyBase64));
        // 获取EC算法的KeyFactory实例，并指定使用BouncyCastleProvider
        KeyFactory keyFactory = KeyFactory.getInstance("EC", new BouncyCastleProvider());
        // 生成PublicKey对象
        PublicKey publickey = keyFactory.generatePublic(publicKeySpec);

        // 将生成的PublicKey对象转换为BCECPublicKey对象
        BCECPublicKey localECPublicKey = (BCECPublicKey) publickey;
        // 获取EC参数规范
        ECParameterSpec localECParameterSpec = localECPublicKey.getParameters();
        // 创建ECDomainParameters对象
        ECDomainParameters localECDomainParameters = new ECDomainParameters(localECParameterSpec.getCurve(),
                    localECParameterSpec.getG(), localECParameterSpec.getN());
        // 创建ECPublicKeyParameters对象
        ECPublicKeyParameters localECPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(), localECDomainParameters);

        // 将原始数据转换为字节数组
        byte[] orginalDataBytes = orginalData.getBytes();

        // 创建SM2Engine对象
        SM2Engine localSM2Engine = new SM2Engine();
        // 初始化SM2Engine对象，并设置为加密模式
        localSM2Engine.init(true, new ParametersWithRandom(localECPublicKeyParameters, new SecureRandom()));
        // 加密原始数据字节数组
        byte[] arrayOfByte2;
        arrayOfByte2 = localSM2Engine.processBlock(orginalDataBytes, 0, orginalDataBytes.length);
        // 返回加密后的字节数组
        return arrayOfByte2;
    }

    /**
     * 使用私钥解密经过SM2算法加密的数据
     *
     * @param privateKeyBase64 Base64编码的私钥字符串
     * @param encryptedDataBase64 Base64编码的加密数据字符串
     * @return 解密后的字节数组
     * @throws NoSuchAlgorithmException 当指定的算法无效时抛出
     * @throws InvalidKeySpecException 当密钥规范无效时抛出
     * @throws InvalidCipherTextException 当密文无效时抛出
     */
    public static byte[] decrypt(String privateKeyBase64, String encryptedDataBase64) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidCipherTextException {
        // 将Base64编码的私钥转换为PKCS8EncodedKeySpec对象
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyBase64));
        // 使用BouncyCastleProvider实例化KeyFactory，指定算法为EC
        KeyFactory keyFactory = KeyFactory.getInstance("EC", new BouncyCastleProvider());
        // 生成PrivateKey对象
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);

        // 将Base64编码的加密数据转换为字节数组
        byte[] encryptedDataBytes = Base64.getDecoder().decode(encryptedDataBase64);

        // 将生成的PrivateKey对象转换为BCECPrivateKey对象
        BCECPrivateKey sm2PriK = (BCECPrivateKey) privateKey;
        // 获取EC参数规范
        ECParameterSpec localECParameterSpec = sm2PriK.getParameters();
        // 使用EC参数规范实例化ECDomainParameters对象
        ECDomainParameters localECDomainParameters = new ECDomainParameters(localECParameterSpec.getCurve(),
                localECParameterSpec.getG(), localECParameterSpec.getN());
        // 实例化ECPrivateKeyParameters对象，使用私钥参数和EC域参数
        ECPrivateKeyParameters localECPrivateKeyParameters = new ECPrivateKeyParameters(sm2PriK.getD(),
                localECDomainParameters);

        // 创建SM2Engine对象用于解密操作
        SM2Engine localSM2Engine = new SM2Engine();
        // 初始化SM2Engine对象为解密操作
        localSM2Engine.init(false, localECPrivateKeyParameters);

        // 使用SM2Engine对象解密数据并返回解密后的字节数组
        return localSM2Engine.processBlock(encryptedDataBytes, 0, encryptedDataBytes.length);
    }
}
